<?php
/**
 * 基于数据表的通用数据模型
 * @author	mfy	2013-03-29 21:20:58
 * @link	http://topcoder.me
 */
 
class Model_Base{
	private $table;				//表名,子类初始化时传入
	private $param = array();		//参数变量,数组形式
	private $o_param = array();		//原始参数变量,数组形式
	private $cols = array();		//表字段,校验用
	private $db;					//数据库对象
	private $where;				//初始化参数
	private $field;				//所需字段
	private $order;				//排序
	
	//多数据获取分页相关
	private $stack = array();		//当前页容器
	private $page_num = 1;		//当前页码
	private $page_size = 20;		//单页容量
	private $total = 0;			//符合的数据总量
	private $cur_p = 0;			//数据集游标
	
	/**
	 * 实例化指定数据表.必须传入表名
	 * 可以只传表名,实例化一个空对象(但字段已读入);
	 * 可以多传入一个数组,给予此新对象初始的属性(字段)值;
	 * 可以多传入一个数组,如array('id'=>1),并传入需获取的字段,则会从数据库读取id=1的数据行实例化;
	 * 也可以传入复杂些的筛选条件,具体看代码;
	 * 可以传入需获取的字段,减少数据传输量,不传则不从数据库拉取数据!
	 * @param	$table		String	数据表名
	 * @param	$where		Array	初始化数据
	 * @param	$field		String	获取的字段
	 * @param	$pagenum	Int		当前页码(要获取第几页)
	 * @param	$pagesize	Int		页面容量
	 */
	public function __construct($table,$where = null,$field = null,$order="",$pagenum = 1,$pagesize=20){
		if (!defined('BASEDIR')) exit('Sorry, you went into a wrong way!');
		$this->table = $table; $this->where = $where; $this->field = $field; $this->order=$order;
		$this->page_num = is_int($pagenum)&&$pagenum>0?$pagenum:1;
		$this->page_size = is_int($pagesize)&&$pagesize>0?$pagesize:20;
		global $db;
		$this->db = &$db;	//获取数据库操作类
		$this->get_cols();
		$this->getInstance($where,$field);
	}
	
	/**
	 * 实例化一个(或多个)对象
	 */
	protected function getInstance($where,$field){
		if(is_int($where)){
			$where = array('id'=>$where);	//默认主键为ID
			$this->getInstance($where);			
		}elseif(is_array($where)){
			if($field){					//从数据中获取对象
				$presql = array(); $param = array();
				foreach ($where as $k => $v) {
					$presql []= strpos($k, '?') ? $k : "`".$k."`=?";
					if(is_array($v)){	//单参数对多参数绑定时处理参数数组
						$param += $v; 
					}else{
						$param []= $v;
					}
				}
				//获取符合条件的数据条数
				$this->total = $this->db->get_one("select count(1) from ".$this->table." where ".
					implode(" AND ", $presql)." limit ".($this->page_num-1)*$this->page_size.
					",".$this->page_size,$param);
				if($this->total){
					//获取符合条件的数据集				
					$this->stack = $this->db->get_all("select ".$field." from ".$this->table." where ".
						implode(" AND ", $presql).($this->order?" order by ".$this->order:"")." limit ".($this->page_num-1)*$this->page_size.
						",".$this->page_size,$param);
					$this->v($this->stack[0]);
					$this->o_v($this->stack[0]);
				}else{
					return false;
				}
			}else{
				//传入数组,则初始化新对象
				foreach ($where as $k => $v) {
					$this->v($k,$v);
				}
			}
		}//不传,初始化空对象
	}

	/**
	 * 返回对象集数量
	 */
	public function total(){
		return $this->total;
	}
	
	/**
	 * 返回当前数据集
	 */
	public function stack(){
		return $this->stack;
	}
	
	/**
	 * 当前数据集游标下移
	 */
	public function fetch(){
		if($this->cur_p<count($this->stack)){
			unset($this->param);
			foreach($this->stack[$this->cur_p] as $k=>$v){
				$this->v($k,$v);
			}			
			$this->cur_p += 1;
			return true;
		}else{	//超出范围
			return false;
		}
	}
	
	/**
	 * 重置当前数据集游标
	 */
	public function reset(){
		unset($this->param);
		if(!empty($this->stack)){		
			foreach($this->stack[0] as $k=>$v){
				$this->v($k,$v);
			}
		}
	}
	
	/**
	 * 翻页
	 */
	public function toPage($page=1,$page_size=null){
		$page_size = is_int($page_size)&&$page_size>0 ? $page_size : $this->page_size; 
		$this->__construct($this->table,$this->where,$this->field,$this->order,$page,$page_size);
	}
	
	/**
	 * 获取param
	 */
	public function getParam(){
		return $this->param;
	}
	
	/**
	 * 直接转向mysqli原生语句
	 */
	public function __call($method, $args){
		if(in_array($method, array('autocommit','commit','rollback'))){
			@call_user_func_array(array($this->db,$method),$args);
		}else{
			exit("MyDb类中无此方法:".$method);
		}
	}

	
	/**
	 * 设置\获取参数值
	 * ①传入字段名获取变量值;
	 * ②传入(字段,值)来设置;
	 * ③传入键值对数组,进行批量设置
	 */
	public function v($arg){
		if(func_num_args()>1){
			//设置变量值
			$v = func_get_arg(1);
			if($this->check_v($arg, $v)){
				$this->param[$arg] = $v;
			}
		}elseif(is_array($arg)){
			//使用数组批量设置变量值
			foreach($arg as $k=>$v){
				$this->v($k,$v);
			}
		}else{
			//获取变量值
			return isset($this->param[$arg])?$this->param[$arg]:null;
		}
		return $this;
	}
	
	/**
	 * 设置原始参数值
	 */
	public function o_v($arg){
		if(func_num_args()>1){
			//设置变量值
			$v = func_get_arg(1);
			if($this->check_v($arg, $v)){
				$this->o_param[$arg] = $v;
			}
		}elseif(is_array($arg)){
			//使用数组批量设置变量值
			foreach($arg as $k=>$v){
				$this->o_v($k,$v);
			}
		}else{
			//获取变量值
			return isset($this->o_param[$arg])?$this->o_param[$arg]:null;
		}
		return $this;
	}
	
	/**
	 * 获取表的全部字段名
	 */
	protected function get_cols(){
		if(!$this->table) exit("尚未设置表名!");
		$res = $this->db->show_cols($this->table);
		if(!$res) exit('获取表字段失败,可能表名有误!');
		foreach($res as $row){
			$this->cols[$row['Field']] = $row;
		}
	}
	
	/**
	 * 校验设置变量值时用到
	 */
	protected function check_v($k,$v){
		if(!$this->cols) $this->get_cols();
		if(!in_array($k, array_keys($this->cols))){		//键值不是字段名,抛弃
			echo "传入了非法键值$k\n";
			return false;
		}
		return true;
		//TODO:之后可加入数据类型和null值校验
	}
	
	/**
	 * 插入新数据
	 */
	public function insert(){
		$param = $this->param;
		if(isset($param['id']))	unset($param['id']);	//插入时去掉键值
		$cols_arr = array_keys($param);
		$sql = "insert into ".$this->table."(".implode(',', $cols_arr).") values(?".str_repeat(",?", count($cols_arr)-1).")";
		return $this->db->insert($sql,$param);
	}
	
	/**
	 * 更新数据
	 */
	public function update($where=array()){
		if(empty($where) && empty($this->where)){	//不允许不设置更新条件更新
			die('不允许不设置更新条件更新');
		}		
		if(!empty($where)){
			$wheresql = array(); $whereparam = array();
			foreach ($where as $k => $v) {
				$wheresql []= strpos($k, '?') ? $k : "`".$k."`=?";
				if(is_array($v)){	//单参数对多参数绑定时处理参数数组
					$whereparam += $v; 
				}else{
					$whereparam []= $v;
				}
			}
			$do_up_arr = array_diff_assoc($this->param, $this->o_param);	//去掉不必要的更新字段,减少数据传输量
			$arr1 = array(); $arr2 = array();
			foreach ($do_up_arr as $k => $v) {
				if($k=='id') continue;		//去掉键值
				$arr1 []= '`'.$k.'`=?';
				$arr2 []= $v;
			}		
			//var_dump("update ".$this->table." set ".implode(',',$arr1)." where ".implode(" and ",$wheresql),array_merge($arr2,$whereparam));		
			return $this->db->update("update ".$this->table." set ".implode(',',$arr1)." where ".implode(" and ",$wheresql),array_merge($arr2,$whereparam));
		}else{	//不传更新条件则使用初始化的筛选条件
			$this->update($this->where);
		}		
	}
	
	/**
	 * 删除数据
	 */
	public function delete($where=array()){
		if(empty($where) && empty($this->where)){	//不允许不设置更新条件更新
			die('不允许不设置更新条件删除');
		}		
		if(!empty($where)){
			$wheresql = array(); $whereparam = array();
			foreach ($where as $k => $v) {
				$wheresql []= strpos($k, '?') ? $k : "`".$k."`=?";
				if(is_array($v)){	//单参数对多参数绑定时处理参数数组
					$whereparam += $v; 
				}else{
					$whereparam []= $v;
				}
			}
			return $this->db->delete("delete from ".$this->table." where ".implode(" and ",$wheresql),$whereparam);
		}else{	//不传更新条件则使用初始化的筛选条件
			$this->delete($this->where);
		}
	}
}